﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using Microsoft.Services.Clab.UI.Mvvmc.Properties;

namespace Microsoft.Services.Clab.UI.Mvvmc
{
    public abstract class CodeControllerBase : IController
    {
        #region Private members
        private Guid controllerId;
        private string logicalName;
        private IViewModel viewModel;
        private ControllerState state;
        private string[] actionNames;
        private bool startupActionExecuted;
        internal List<Tuple<string, ExecuteActionSignature<object[]>, CanExecuteActionSignature<object[]>>> MappedActions { get; set; }

        private bool DoActionCore(string actionName, object[] actionParameters, bool throwIfActionNotFound, Func<Exception, Exception> exceptionMessageFunc)
        {
            try
            {
                var item = this.MappedActions.FirstOrDefault(t => t.Item1 == actionName);
                if (item != null)
                {
                    try
                    {
                        DispatcherProxy.Invoke(() =>
                        {
                            this.State = ControllerState.Busy;
                        });

                        DispatcherProxy.Invoke(() =>
                        {
                            item.Item2(actionParameters);
                        }, DispatcherProxyPriority.BelowUIThreadPriority);

                        return true;
                    }
                    finally
                    {
                        DispatcherProxy.Invoke(() =>
                        {
                            if (this.State < ControllerState.Disposing)
                            {
                                this.State = ControllerState.Ready;
                            }
                        });
                    }
                }
                else
                {
                    if (throwIfActionNotFound)
                    {
                        throw new ControllerException(string.Format(Resources.ActionNotRegistered, actionName));
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            catch (Exception ex)
            {
                var handled = ExceptionHandler.HandleWithExceptionHandler(ref ex, exceptionMessageFunc, DefaultExceptionHandler);
                if (!handled)
                {
                    throw ex;
                }
                else
                {
                    return false;
                }
            }
        }

        private void Views_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (this.ViewModel.Views.Count == 0)
            {
                this.Dispose();
            }
        }

        private void EnsureNotDisposed()
        {
            if (this.State == ControllerState.Disposed)
            {
                throw new ControllerDisposedException(this.GetType().FullName, Resources.NotAvailableOnDisposedController.FormatEx(this.ControllerId));
            }
        }
        #endregion

        #region ctor

        protected CodeControllerBase()
        {
            this.ControllerId = Guid.NewGuid();
            this.ChildControllers = new ControllerCollection(this, state => this.State = state);
            this.State = ControllerState.NotInitialized;
            this.CustomPropertyBag = new KeyValueCollection();
            this.MappedActions = new List<Tuple<string, ExecuteActionSignature<object[]>, CanExecuteActionSignature<object[]>>>();
        }

        #endregion

        #region Protected methods
        protected virtual void OnInitialized()
        {
        }

        protected abstract IViewModel CreateViewModel();

        protected virtual void CacheMetadata(ControllerMetadata metadata)
        {
        }

        #endregion

        #region IController Members
        public Guid ControllerId
        {
            get
            {
                return this.controllerId;
            }
            private set
            {
                if (this.controllerId != value)
                {
                    this.controllerId = value;
                    this.OnPropertyChanged("ControllerId");
                }
            }
        }

        public void Initialize(IController parentController, object initParameters)
        {
            this.State = ControllerState.Initializing;

            if (initParameters == null)
            {
                this.InitParameters = new KeyValueCollection();
            }
            else
            {
                this.InitParameters = initParameters as KeyValueCollection;
                this.InitParameter = initParameters;
            }
            this.ParentController = parentController;

            var createdViewModel = CreateViewModel();
            var parentViewModel = parentController != null ? parentController.ViewModel : null;

            createdViewModel.Initialize(this, parentViewModel, initParameters);
            createdViewModel.Views.CollectionChanged += Views_CollectionChanged;
            this.ViewModel = createdViewModel;

            this.CacheMetadata(new ControllerMetadata(this));
            this.ActionNames = this.MappedActions.Select(a => a.Item1).ToArray();

            this.OnInitialized();
            this.State = ControllerState.Initialized;
        }

        public object InitParameter { get; private set; }

        public KeyValueCollection InitParameters { get; private set; }

        public void DoStartupAction()
        {
            if (this.startupActionExecuted)
            {
                return;
            }

            this.EnsureNotDisposed();

            this.startupActionExecuted = true;
            this.DoActionCore(ControllerMetadata.StartupActionName, null, false, ex => new ControllerException(string.Format(Resources.ExceptionInDoStartupAction, this.ControllerId), this.ControllerId, null, ex));
        }

        public void DoAction(string actionName, params object[] actionParameters)
        {
            this.EnsureNotDisposed();

            this.DoActionCore(actionName, actionParameters, true, ex => new ControllerException(string.Format(Resources.ExceptionInDoAction, actionName, this.ControllerId), this.ControllerId, actionName, ex));
        }

        public bool CanDoAction(string actionName, params object[] actionParameters)
        {
            this.EnsureNotDisposed();

            var item = this.MappedActions.FirstOrDefault(t => t.Item1 == actionName);
            if (item != null)
            {
                try
                {
                    var canExecute = true;
                    if (item.Item3 != null)
                    {
                        item.Item3(ref canExecute, actionParameters);
                    }

                    return canExecute;
                }
                catch (Exception ex)
                {
                    var handled = ExceptionHandler.HandleWithExceptionHandler(
                        ref ex,
                        ex1 => new ControllerException(string.Format(Resources.ExceptionInCanDoAction, actionName, this.ControllerId), this.ControllerId, actionName, ex1),
                        DefaultExceptionHandler);
                    if (!handled)
                    {
                        throw ex;
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            else
            {
                throw new ControllerException(string.Format(Resources.ActionNotRegistered, actionName));
            }
        }

        public bool ExistsAction(string actionName)
        {
            this.EnsureNotDisposed();

            return this.MappedActions.Any(t => t.Item1 == actionName);
        }

        public bool TryDoAction(string actionName, params object[] actionParameters)
        {
            this.EnsureNotDisposed();

            if (this.ExistsAction(actionName) && this.CanDoAction(actionName, actionParameters))
            {
                this.DoAction(actionName, actionParameters);
                return true;
            }
            return false;
        }

        public void UnloadView(IView view)
        {
            this.EnsureNotDisposed();

            var executed = this.DoActionCore(ControllerMetadata.UnloadViewActionName, new object[] { view }, false, ex => new ControllerException(string.Format(Resources.ExceptionInUnloadViewAction, this.ControllerId), this.ControllerId, null, ex));
            if (!executed)
            {
                view.Dispose();
            }
        }

        public string LogicalName
        {
            get
            {
                this.EnsureNotDisposed();

                return this.logicalName;
            }
            set
            {
                this.EnsureNotDisposed();

                if (this.logicalName != value)
                {
                    this.logicalName = value;
                    this.OnPropertyChanged("LogicalName");
                }
            }
        }

        public IController ParentController { get; private set; }

        public ControllerCollection ChildControllers { get; private set; }

        public IViewModel ViewModel
        {
            get
            {
                this.EnsureNotDisposed();

                return this.viewModel;
            }
            private set
            {
                if (this.viewModel != value)
                {
                    this.viewModel = value;
                    this.OnPropertyChanged("ViewModel");
                }
            }
        }

        public ControllerState State
        {
            get
            {
                return this.state;
            }
            protected set
            {
                if (this.state != value)
                {
                    this.state = value;
                    this.OnPropertyChanged("State");
                }
            }
        }

        public string[] ActionNames
        {
            get
            {
                this.EnsureNotDisposed();

                return this.actionNames;
            }
            private set
            {
                if (this.actionNames != value)
                {
                    this.actionNames = value;
                    this.OnPropertyChanged("ActionNames");
                }
            }
        }

        public KeyValueCollection CustomPropertyBag { get; private set; }

        #endregion

        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;

            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion

        #region Dispose pattern

        ~CodeControllerBase()
        {
            this.Dispose(false);
        }

        public void Dispose()
        {
            if (this.State < ControllerState.Disposing)
            {
                try
                {
                    this.State = ControllerState.Disposing;

                    this.ChildControllers.Clear();
                    if (this.ViewModel != null)
                    {
                        this.ViewModel.Views.Clear();
                    }
                    this.Dispose(true);
                }
                finally
                {
                    this.State = ControllerState.Disposed;
                }
            }
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
        }
        #endregion

        public static Func<Exception, bool> DefaultExceptionHandler;
    }
}
